/**
  ******************************************************************************
  * @file    bsp_rtc.c
  * @author  Iron
  * @date    2018-07-23
  * @version v1.0
  * @brief   bsp rtc c file
  */

/** @addtogroup GROUP_BSP
  * @{
  */

/* includes ------------------------------------------------------------------*/
#include "FreeRTOS.h"
#include "semphr.h"
#include "stm32f4xx_hal.h"
#include "bsp_rtc.h"

// DEBUG
#include "debug_log.h"
#define LOG_LOCAL_LEVEL DBG_LOG_DEBUG
DBG_LOG_TAG("RTC");

/* private typedef -----------------------------------------------------------*/
/* private macro -------------------------------------------------------------*/
#define BSP_RTC_RTOS_ENTER_CRITICAL()  portENTER_CRITICAL()
#define BSP_RTC_RTOS_EXIT_CRITICAL()   portEXIT_CRITICAL()

/**
  * @breaf rtc mutex handle
  */
static SemaphoreHandle_t bsp_rtc_mutex_handle;
static StaticSemaphore_t bsp_rtc_mutex;

#define BSP_RTC_OS_MUTEX_INIT() \
    do{bsp_rtc_mutex_handle = xSemaphoreCreateMutexStatic(&bsp_rtc_mutex);}while(0)

#define BSP_RTC_OS_MUTEX_WAIT(timeout) \
    do{                                                                                                                                                          \
        if( xSemaphoreTake(bsp_rtc_mutex_handle, pdMS_TO_TICKS(timeout)) != pdPASS) {    \
            DBG_LOGE(TAG, "rtc mutex wait error or timeout.");                           \
            return BSP_RTC_FAIL;                                                                                                     \
        }                                                                                                                                                            \
    }while(0)

#define BSP_RTC_OS_MUTEX_RELEASE() \
    do{xSemaphoreGive(bsp_rtc_mutex_handle);}while(0)

#define BSP_RTC_OS_MUTEX_WAIT_TIEMOUT  100  // ms

/* private variables ---------------------------------------------------------*/
RTC_HandleTypeDef hrtc;

/* private function prototypes -----------------------------------------------*/
/* private functions ---------------------------------------------------------*/
extern void _Error_Handler(char *file, int line);
uint32_t bsp_get_time_ms(void);

/* RTC init function */
void MX_RTC_Init(void)
{
    RTC_TimeTypeDef sTime;
    RTC_DateTypeDef sDate;

    memset(&sTime, 0, sizeof(RTC_TimeTypeDef));
    memset(&sDate, 0, sizeof(RTC_DateTypeDef));

    /**Initialize RTC Only
    */
    hrtc.Instance = RTC;
    hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
    hrtc.Init.AsynchPrediv = 127;
    hrtc.Init.SynchPrediv = 255;
    hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
    hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
    hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;

    if (HAL_RTC_Init(&hrtc) != HAL_OK)
    {
        _Error_Handler(__FILE__, __LINE__);
    }

    HAL_RTCEx_DeactivateTimeStamp(&hrtc);
    HAL_RTCEx_DeactivateTamper(&hrtc, RTC_TAMPER_1);
    HAL_RTCEx_DeactivateTamper(&hrtc, RTC_TAMPER_2);
    HAL_RTCEx_DeactivateWakeUpTimer(&hrtc);
    HAL_RTCEx_DeactivateCoarseCalib(&hrtc);
    HAL_RTCEx_DeactivateCalibrationOutPut(&hrtc);
    HAL_RTCEx_DeactivateRefClock(&hrtc);

    /**Initialize RTC and set the Time and Date
    */
    if (HAL_RTCEx_BKUPRead(&hrtc, RTC_BKP_DR0) != 0xABCD)
    {
        sTime.Hours = 0;
        sTime.Minutes = 0;
        sTime.Seconds = 0;
        sTime.SubSeconds = 0;
        sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
        sTime.StoreOperation = RTC_STOREOPERATION_RESET;

        sDate.WeekDay = RTC_WEEKDAY_THURSDAY;
        sDate.Month = RTC_MONTH_JANUARY;
        sDate.Date = 01;
        sDate.Year = 18;

        BSP_RTC_RTOS_ENTER_CRITICAL();

        if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) != HAL_OK)
        {
            _Error_Handler(__FILE__, __LINE__);
        }

        if (HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN) != HAL_OK)
        {
            _Error_Handler(__FILE__, __LINE__);
        }

        HAL_RTCEx_BKUPWrite(&hrtc, RTC_BKP_DR0, 0xABCD);

        BSP_RTC_RTOS_EXIT_CRITICAL();
    }
}

void HAL_RTC_MspInit(RTC_HandleTypeDef *rtcHandle)
{
    if (rtcHandle->Instance == RTC)
    {
        /* RTC clock enable */
        __HAL_RCC_RTC_ENABLE();
    }
}

void HAL_RTC_MspDeInit(RTC_HandleTypeDef *rtcHandle)
{
    if (rtcHandle->Instance == RTC)
    {
        /* Peripheral clock disable */
        __HAL_RCC_RTC_DISABLE();
    }
}

void bsp_rtc_init(void)
{
    BSP_RTC_OS_MUTEX_INIT();
    MX_RTC_Init();
}

time_t bsp_get_rtc_time(struct tm *t, time_t *sec, time_t *us)
{
    RTC_TimeTypeDef sTime;
    RTC_DateTypeDef sDate;
    time_t rtc_time;
    struct tm now_tm;

    BSP_RTC_OS_MUTEX_WAIT(BSP_RTC_OS_MUTEX_WAIT_TIEMOUT);

    memset(&now_tm, 0, sizeof(now_tm));
    memset(&sTime, 0, sizeof(RTC_TimeTypeDef));
    memset(&sDate, 0, sizeof(RTC_DateTypeDef));

    BSP_RTC_RTOS_ENTER_CRITICAL();

    HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
    HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);

    BSP_RTC_RTOS_EXIT_CRITICAL();

    now_tm.tm_year = sDate.Year + 100;
    now_tm.tm_mon = sDate.Month - 1;
    now_tm.tm_mday = sDate.Date;
    now_tm.tm_hour = sTime.Hours;
    now_tm.tm_min = sTime.Minutes;
    now_tm.tm_sec = sTime.Seconds;

    rtc_time = mktime(&now_tm);

    if (t != NULL)
    {
        memset(t, 0, sizeof(struct tm));
        *t = now_tm;
    }

    if (sec != NULL)
    {
        *sec = rtc_time;
    }

    if (us != NULL)
    {
        *us = (256 - sTime.SubSeconds) * 1000000 / 256;
    }

    BSP_RTC_OS_MUTEX_RELEASE();
    return rtc_time;
}

int32_t bsp_set_rtc_time(time_t sec, time_t us)
{
    RTC_TimeTypeDef sTime;
    RTC_DateTypeDef sDate;
    struct tm now_tm;
//  uint32_t os_time1, os_time2, os_time3;

    BSP_RTC_OS_MUTEX_WAIT(BSP_RTC_OS_MUTEX_WAIT_TIEMOUT);

    memset(&now_tm, 0, sizeof(now_tm));
    memset(&sTime, 0, sizeof(RTC_TimeTypeDef));
    memset(&sDate, 0, sizeof(RTC_DateTypeDef));

    bsp_rtc_gmtime_r(&sec, &now_tm);

    sTime.Hours = now_tm.tm_hour;
    sTime.Minutes = now_tm.tm_min;
    sTime.Seconds = now_tm.tm_sec;
    sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    sTime.StoreOperation = RTC_STOREOPERATION_RESET;

    sDate.Year = now_tm.tm_year - 100;
    sDate.Month = now_tm.tm_mon + 1;
    sDate.Date = now_tm.tm_mday;
    sDate.WeekDay = now_tm.tm_wday + 1;

//  os_time1 = bsp_get_time_ms();  // test

    if (us < 1000000)
    {
        uint32_t SSR_value = hrtc.Instance->SSR;
        uint32_t ShiftSubFS = ((1000000 - us) * 256) / 1000000; // max <= 0x7FFF
        if (ShiftSubFS > SSR_value)
        {
            HAL_RTCEx_SetSynchroShift(&hrtc, RTC_SHIFTADD1S_RESET, ShiftSubFS - SSR_value);
        }
        else
        {
            HAL_RTCEx_SetSynchroShift(&hrtc, RTC_SHIFTADD1S_SET, SSR_value - ShiftSubFS);
        }
    }

//  os_time2 = bsp_get_time_ms();  // test

    // about 1ms
    if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) == HAL_OK)
    {
        HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
    }

//  os_time3 = bsp_get_time_ms();
//  DBG_LOGW(TAG, "bsp_set_rtc_time() used time1:%d ms, time2:%d ms", os_time3 - os_time1, os_time3 - os_time2);

    BSP_RTC_OS_MUTEX_RELEASE();
    return BSP_RTC_OK;
}

int32_t bsp_set_rtc_date_time(
    int year, int mon, int mday,
    int hour, int min, int sec, int wday)
{
    RTC_TimeTypeDef sTime;
    RTC_DateTypeDef sDate;
//  uint32_t os_time;

    BSP_RTC_OS_MUTEX_WAIT(BSP_RTC_OS_MUTEX_WAIT_TIEMOUT);

    memset(&sTime, 0, sizeof(RTC_TimeTypeDef));
    memset(&sDate, 0, sizeof(RTC_DateTypeDef));

    sTime.Hours = hour;
    sTime.Minutes = min;
    sTime.Seconds = sec;
    sTime.DayLightSaving = RTC_DAYLIGHTSAVING_NONE;
    sTime.StoreOperation = RTC_STOREOPERATION_RESET;

    sDate.Year = year - 100;
    sDate.Month = mon + 1;
    sDate.Date = mday;
    sDate.WeekDay = wday + 1;

//  os_time = bsp_get_time_ms(); // test

    if (HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN) == HAL_OK)
    {
        HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
    }

//  DBG_LOGW(TAG, "bsp_set_rtc_date_time() used time: %d ms", bsp_get_time_ms() - os_time);

    BSP_RTC_OS_MUTEX_RELEASE();
    return BSP_RTC_OK;
}

/**
  * @breaf gmtime_r / localtime_r
  **/

/* seconds per day */
#define SPD 24*60*60

/* days per month -- nonleap! */
static const short __spm[13] =
{
    0,
    (31),
    (31 + 28),
    (31 + 28 + 31),
    (31 + 28 + 31 + 30),
    (31 + 28 + 31 + 30 + 31),
    (31 + 28 + 31 + 30 + 31 + 30),
    (31 + 28 + 31 + 30 + 31 + 30 + 31),
    (31 + 28 + 31 + 30 + 31 + 30 + 31 + 31),
    (31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30),
    (31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31),
    (31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30),
    (31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31),
};

static int __isleap(int year)
{
    /* every fourth year is a leap year except for century years that are
     * not divisible by 400. */
    /*  return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); */
    return (!(year % 4) && ((year % 100) || !(year % 400)));
}

struct tm *bsp_rtc_gmtime_r(const time_t *timep, struct tm *r)
{
    time_t i;
    register time_t work = *timep % (SPD);

    r->tm_sec = work % 60;
    work /= 60;
    r->tm_min = work % 60;
    r->tm_hour = work / 60;
    work = *timep / (SPD);
    r->tm_wday = (4 + work) % 7;
    for (i = 1970;; ++i)
    {
        register time_t k = __isleap(i) ? 366 : 365;
        if (work >= k)
            work -= k;
        else
            break;
    }
    r->tm_year = i - 1900;
    r->tm_yday = work;

    r->tm_mday = 1;
    if (__isleap(i) && (work > 58))
    {
        if (work == 59)
            r->tm_mday = 2; /* 29.2. */
        work -= 1;
    }

    for (i = 11; i && (__spm[i] > work); --i)
        ;
    r->tm_mon = i;
    r->tm_mday += work - __spm[i];
    return r;
}

__weak time_t time(time_t *tmr)
{
    return bsp_get_rtc_time(NULL, tmr, NULL);
}

/**
  * @}
  */

/******************* (C)COPYRIGHT 2018 ***** END OF FILE *********************/
